home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2008 February / PCWFEB08.iso / Software / Freeware / Miro 1.0 / Miro_Installer.exe / xulrunner / python / dl_daemon / daemon.py < prev    next >
Encoding:
Python Source  |  2007-11-12  |  8.1 KB  |  246 lines

  1. from dl_daemon import command
  2. import os
  3. import cPickle
  4. import socket
  5. import traceback
  6. from time import sleep
  7. from struct import pack, unpack, calcsize
  8. import tempfile
  9. import config
  10. import prefs
  11. import eventloop
  12. import util
  13. import logging
  14. from httpclient import ConnectionHandler
  15.  
  16. SIZE_OF_INT = calcsize("I")
  17.  
  18. class DaemonError(Exception):
  19.     """Exception while communicating to a daemon (either controller or
  20.     downloader).
  21.     """
  22.     pass
  23.  
  24. firstDaemonLaunch = '1'
  25. def launchDownloadDaemon(oldpid, port):
  26.     global firstDaemonLaunch
  27.  
  28.     daemonEnv = {
  29.         'DEMOCRACY_DOWNLOADER_PORT' : str(port),
  30.         'DEMOCRACY_DOWNLOADER_FIRST_LAUNCH' : firstDaemonLaunch,
  31.     }
  32.     import app
  33.     delegate = app.delegate
  34.     delegate.launchDownloadDaemon(oldpid, daemonEnv)
  35.     firstDaemonLaunch = '0'
  36.  
  37. def getDataFile():
  38.     try:
  39.         uid = os.getuid()
  40.     except:
  41.         # This works for win32, where we don't have getuid()
  42.         uid = os.environ['USERNAME']
  43.        
  44.     shortAppName = "Miro"
  45.     return os.path.join(tempfile.gettempdir(), ('%s_Download_Daemon_%s.txt' % (shortAppName,uid)))
  46.  
  47. pidfile = None
  48. def writePid(pid):
  49.     """Write out our pid.
  50.  
  51.     This method locks the pid file until the downloader exits.  On windows
  52.     this is achieved by keeping the file open.  On Unix/OS X, we use the
  53.     fcntl.lockf() function.
  54.     """
  55.  
  56.     global pidfile
  57.     # NOTE: we want to open the file in a mode the standard open() doesn't
  58.     # support.  We want to create the file if nessecary, but not truncate it
  59.     # if it's already around.  We can't truncate it because on unix we haven't
  60.     # locked the file yet.
  61.     fd = os.open(getDataFile(), os.O_WRONLY | os.O_CREAT)
  62.     pidfile = os.fdopen(fd, 'w')
  63.     try:
  64.         import fcntl
  65.     except:
  66.         pass
  67.     else:
  68.         fcntl.lockf(pidfile, fcntl.LOCK_EX | fcntl.LOCK_NB)
  69.     pidfile.write("%s\n" % pid)
  70.     pidfile.flush()
  71.     # NOTE: There may be extra data after the line we write left around from
  72.     # prevous writes to the pid file.  This is fine since readPid() only reads
  73.     # the 1st line.
  74.     #
  75.     # NOTE 2: we purposely don't close the file, to achieve locking on
  76.     # windows.
  77.  
  78. def readPid():
  79.     try:
  80.         f = open(getDataFile(), "r")
  81.     except IOError:
  82.         return None
  83.     try:
  84.         try:
  85.             return int(f.readline())
  86.         except ValueError:
  87.             return None
  88.     finally:
  89.         f.close()
  90.  
  91. lastDaemon = None
  92.  
  93. class Daemon(ConnectionHandler):
  94.     def __init__(self):
  95.         ConnectionHandler.__init__(self)
  96.         global lastDaemon
  97.         lastDaemon = self
  98.         self.waitingCommands = {}
  99.         self.returnValues = {}
  100.         self.size = 0
  101.         self.states['ready'] = self.onSize
  102.         self.states['command'] = self.onCommand
  103.         self.queuedCommands = []
  104.         self.shutdown = False
  105.         self.stream.disableReadTimeout = True
  106.         # disable read timeouts for the downloader daemon communication.  Our
  107.         # normal state is to wait for long periods of time for without seeing
  108.         # any data.
  109.  
  110.     def onError(self, error):
  111.         """Call this when an error occurs.  It forces the
  112.         daemon to close its connection.
  113.         """
  114.         logging.warning ("socket error in daemon, closing my socket")
  115.         self.closeConnection()
  116.         raise error
  117.  
  118.     def onConnection(self, socket):
  119.         self.changeState('ready')
  120.         for (comm, callback) in self.queuedCommands:
  121.             self.send(comm, callback)
  122.         self.queuedCommands = []
  123.  
  124.     def onSize(self):
  125.         if self.buffer.length >= SIZE_OF_INT:
  126.             (self.size,) = unpack("I", self.buffer.read(SIZE_OF_INT))
  127.             self.changeState('command')
  128.  
  129.     def onCommand(self):
  130.         if self.buffer.length >= self.size:
  131.             try:
  132.                 comm = cPickle.loads(self.buffer.read(self.size))
  133.             except:
  134.                 logging.exception ("WARNING: error unpickling command.")
  135.             else:
  136.                 self.processCommand(comm)
  137.             self.changeState('ready')
  138.  
  139.     def processCommand(self, comm):
  140.         util.timeTrapCall("Running: %s" % (comm,), self.runCommand, comm)
  141.  
  142.     def runCommand(self, comm):
  143.         comm.setDaemon(self)
  144.         comm.action()
  145.  
  146.     def send(self, comm, callback = None):
  147.         if self.state == 'initializing':
  148.             self.queuedCommands.append((comm, callback))
  149.         else:
  150.             raw = cPickle.dumps(comm, cPickle.HIGHEST_PROTOCOL)
  151.             self.sendData(pack("I",len(raw)) + raw, callback)
  152.  
  153. class DownloaderDaemon(Daemon):
  154.     def __init__(self, port):
  155.         # before anything else, write out our PID 
  156.         writePid(os.getpid())
  157.         # connect to the controller and start our listen loop
  158.         Daemon.__init__(self)
  159.         self.openConnection('127.0.0.1', port, self.onConnection, self.onError)
  160.  
  161.     def handleClose(self, type):
  162.         if self.shutdown:
  163.             return
  164.         self.shutdown = True
  165.         eventloop.quit()
  166.         logging.warning ("downloader: connection closed -- quitting")
  167.         from dl_daemon import download
  168.         download.shutDown()
  169.         import threading
  170.         for thread in threading.enumerate():
  171.             if thread != threading.currentThread() and not thread.isDaemon():
  172.                 thread.join()
  173.  
  174. class ControllerDaemon(Daemon):
  175.     def __init__(self):
  176.         Daemon.__init__(self)
  177.         self.stream.acceptConnection('127.0.0.1', 0, self.onConnection, self.onError)
  178.         self.port = self.stream.port
  179.         launchDownloadDaemon(readPid(), self.port)
  180.         data = {}
  181.         remoteConfigItems = [prefs.LIMIT_UPSTREAM,
  182.                    prefs.UPSTREAM_LIMIT_IN_KBS,
  183.                    prefs.BT_MIN_PORT,
  184.                    prefs.BT_MAX_PORT,
  185.                    prefs.MOVIES_DIRECTORY,
  186.                    prefs.PRESERVE_DISK_SPACE,
  187.                    prefs.PRESERVE_X_GB_FREE,
  188.                    prefs.SUPPORT_DIRECTORY,
  189.                    prefs.SHORT_APP_NAME,
  190.                    prefs.LONG_APP_NAME,
  191.                    prefs.APP_PLATFORM,
  192.                    prefs.APP_VERSION,
  193.                    prefs.APP_SERIAL,
  194.                    prefs.APP_REVISION,
  195.                    prefs.PUBLISHER,
  196.                    prefs.PROJECT_URL,
  197.                    prefs.DOWNLOADER_LOG_PATHNAME,
  198.                    prefs.LOG_PATHNAME,
  199.                    prefs.GETTEXT_PATHNAME,
  200.                 ]
  201.  
  202.         for desc in remoteConfigItems:
  203.             data[desc.key] = config.get(desc)
  204.         c = command.InitialConfigCommand(self, data)
  205.         c.send()
  206.         config.addChangeCallback (self.updateConfig)
  207.  
  208.     def updateConfig (self, key, value):
  209.         if not self.shutdown:
  210.             c = command.UpdateConfigCommand (self, key, value)
  211.             c.send()
  212.             
  213.     def handleClose(self, type):
  214.         if not self.shutdown:
  215.             logging.warning ("Downloader Daemon died")
  216.             # FIXME: replace with code to recover here, but for now,
  217.             # stop sending.
  218.             self.shutdown = True
  219.             config.removeChangeCallback (self.updateConfig)
  220.  
  221.     def shutdown_timeout_cb(self):
  222.         logging.warning ("killing download daemon")
  223.         import app
  224.         delegate = app.delegate
  225.         delegate.killProcess(readPid())
  226.         self.shutdownResponse()
  227.  
  228.     def shutdownResponse(self):
  229.         if self.shutdown_callback:
  230.             self.shutdown_callback()
  231.         self.shutdown_timeout_dc.cancel()
  232.  
  233.     def shutdownDownloaderDaemon(self, timeout=5, callback = None):
  234.         """Send the downloader daemon the shutdown command.  If it doesn't
  235.         reply before timeout expires, kill it.  (The reply is not sent until
  236.         the downloader daemon has one remaining thread and that thread will
  237.         immediately exit).
  238.         """
  239.         self.shutdown_callback = callback
  240.         c = command.ShutDownCommand(self)
  241.         c.send()
  242.         self.shutdown = True
  243.         config.removeChangeCallback (self.updateConfig)
  244.         self.shutdown_timeout_dc = eventloop.addTimeout(timeout, self.shutdown_timeout_cb, "Waiting for dl_daemon shutdown")
  245.  
  246.